package com.zch.starter.sentinel.springboot.starter.config;

import com.alibaba.csp.sentinel.datasource.FileRefreshableDataSource;
import com.alibaba.csp.sentinel.datasource.ReadableDataSource;
import com.alibaba.csp.sentinel.datasource.nacos.NacosDataSource;
import com.alibaba.csp.sentinel.init.InitExecutor;
import com.alibaba.csp.sentinel.slots.block.flow.FlowRule;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.PropertyKeyConst;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.exception.NacosException;
import com.zch.starter.sentinel.springboot.starter.properties.SentinelNacosAutoConfigProperties;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.List;
import java.util.Properties;

/**
 * 类执行顺序：ConditionalOnProperty条件满足-->spring创建SentinelNacosAutoConfiguration实例--》spring
 * 创建SentinelNacosAutoConfigProperties-->spring 注入成员变量sentinelNacosAutoConfigProperties--》@PostConstruct
 * 方法执行--》@Bean方法执行
 */
@ConditionalOnProperty(prefix = "sentinel.autoconfig", name = "enabled", havingValue = "true", matchIfMissing = false)
@EnableConfigurationProperties(value = {SentinelNacosAutoConfigProperties.class})
public class SentinelNacosAutoConfiguration {


    @Resource
    SentinelNacosAutoConfigProperties sentinelNacosAutoConfigProperties;
    //spring 的环境上下文类
    @Resource
    Environment environment;

    /**
     * @PostConstruct 是指spring 创建好bean后 且属性被注入赋值结束后 才执行，他比里的@Bean 方法先执行
     *
     *
     * 属性设置完后 设置系统变量效果跟启动命令 JVM -D 一样的
     * 也可以在启动命令里做：https://github.com/alibaba/Sentinel/wiki/%E5%90%AF%E5%8A%A8%E9%85%8D%E7%BD%AE%E9%A1%B9
     * -Dproject.name=client-biz-test # 指定当前客户端的名称
     * -Dcsp.sentinel.dashboard.server=127.0.0.1:8080 # 指定远程的dashoard的地址。
     */
    @PostConstruct
    public void init() {
        String appName  =environment.getProperty("sentinel.client.appName");
        // 效果跟启动命令方式一样( -Dproject.name=***) sentinel 框架层会自动从系统配置文件中获取相关值 跟控制台建立连接发送心跳
        System.setProperty("project.name", sentinelNacosAutoConfigProperties.getClient().getAppName());
        System.setProperty("csp.sentinel.dashboard.server", sentinelNacosAutoConfigProperties.getDashboard().getServerAddress());
        //sentinel-core 架包中代码
        InitExecutor.doInit(); // 初始化 Sentinel 确保连接控制台的相关参数都已经初始化后再执行
    }

    /**
     * 通过nacos 手动更新配置 ConfigService
     *  final String groupId = "Sentinel_Demo";
     *         final String dataId = "com.alibaba.csp.sentinel.demo.flow.rule";
     *         final String rule = "[\n"
     *             + "  {\n"
     *             + "    \"resource\": \"TestResource\",\n"
     *             + "    \"controlBehavior\": 0,\n"
     *             + "    \"count\": 5.0,\n"
     *             + "    \"grade\": 1,\n"
     *             + "    \"limitApp\": \"default\",\n"
     *             + "    \"strategy\": 0\n"
     *             + "  }\n"
     *             + "]";
     *         System.out.println(configService.publishConfig(dataId, groupId, rule));
     * @return
     * @throws NacosException
     */
    @Bean("configService")
    public ConfigService configService() throws NacosException {
        //这种连接地址可能会需要账密 就连接不通了，改用个方式
       // ConfigService configService =NacosFactory.createConfigService(sentinelNacosAutoConfigProperties.getServerAddress());
        Properties properties = buildProperties(sentinelNacosAutoConfigProperties);
        //NacosFactory 也是有nacos-api j架包提供
        ConfigService configService =NacosFactory.createConfigService(properties);
        return configService;
    }


    @Bean("flowRuleDataSource")
    @ConditionalOnMissingBean //只创建一个bean
    public ReadableDataSource<String, List<FlowRule>> flowRuleDataSource() {
        Properties properties = buildProperties(sentinelNacosAutoConfigProperties);
        SentinelNacosAutoConfigProperties.Nacos nacos = sentinelNacosAutoConfigProperties.getNacos();
        String groupId = nacos.getGroupId();
        String dataId = nacos.getDataId();
        ReadableDataSource<String, List<FlowRule>> flowRuleDataSource = new NacosDataSource<>(properties, groupId, dataId,
                source -> JSON.parseObject(source, new TypeReference<List<FlowRule>>() {
                }));
        return flowRuleDataSource;
    }

    @Bean("sentinelNacosConfig")
    @ConditionalOnMissingBean
    public SentinelNacosConfig sentinelNacosConfig(ReadableDataSource<String, List<FlowRule>> flowRuleDataSource){
        SentinelNacosConfig sentinelNacosConfig = new SentinelNacosConfig();
        sentinelNacosConfig.setFlowRuleDataSource(flowRuleDataSource);
        return sentinelNacosConfig;
    }
    /**
     * 构建通用得properties 里面包含 nacos (地址 用户名 密码  命名空间)
     * @param sentinelNacosAutoConfigProperties
     * @return
     */
    private  Properties buildProperties(SentinelNacosAutoConfigProperties sentinelNacosAutoConfigProperties) {
        Properties properties = new Properties();
        //PropertyKeyConst jar来源：nacos-api->>sentinel-datasource-nacos
        SentinelNacosAutoConfigProperties.Nacos nacos = sentinelNacosAutoConfigProperties.getNacos();
        properties.put(PropertyKeyConst.SERVER_ADDR, nacos.getServerAddress());
        properties.put(PropertyKeyConst.USERNAME, nacos.getUsername());
        properties.put(PropertyKeyConst.PASSWORD, nacos.getPassword());
        //如何不指定命名空间 Nacos 会默认将所有配置放在内置的 public 命名空间下（其ID为""空字符串）
        if(StringUtils.isNotBlank(nacos.getNamespace())){
            properties.put(PropertyKeyConst.NAMESPACE,nacos.getNamespace());
        }
        return properties;
    }


}
